/**
 * 
 */
package xiaojian.toolkit.fsm.core;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.collections4.Closure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import xiaojian.toolkit.fsm.DefaultFSMContext;
import xiaojian.toolkit.fsm.FiniteStateMachine;


/**
 * @author isdom
 *
 */
public class SmartContextImpl extends DefaultFSMContext implements SmartContext {

    private static final Logger LOG = 
    		LoggerFactory.getLogger(SmartContextImpl.class);
    
	private	final Queue<Object> _pendingEvents = new ConcurrentLinkedQueue<Object>();
	
	private	final AtomicBoolean	_isActive = new AtomicBoolean(false);
	
	private	StatusListener		_statusListener = null;
	
	private volatile long		_lastActiveTime;
	
	private	final AtomicLong	_activeTime = new AtomicLong(0);

    private final Map<String,Long> _stateLiveTime = new HashMap<String, Long>();
	
	public SmartContextImpl(final FiniteStateMachine fsm) {
		super(fsm);
	}
	
	/* (non-Javadoc)
	 * @see com.skymobi.fsm.DefaultFSMContext#destroy()
	 */
	@Override
	public List<Object> destroy() {
		setUnactive();
		
		//	set destroy flag
		super.destroy();

		//	对于pending event 为空的情况，直接返回null
		if ( !_pendingEvents.isEmpty() ) {
			final List<Object> ret = new ArrayList<Object>(_pendingEvents);
			_pendingEvents.clear();
			return ret;
		}
		else {
			return null;
		}
	}

	private Object popPendingEvent() {
		if ( isDestroyed() ) {
			return	null;
		}
		return _pendingEvents.poll();
	}

	private void pushPendingEvent(final Object event) {
		if ( !isDestroyed() ) {
			_pendingEvents.add(event);
		}
		else {
			LOG.warn("ctx {} of {} already destroy, ignore pending event {} ", 
					new Object[]{this, fsm, event});
		}
	}

	private boolean hasPendingEvent() {
		if ( isDestroyed() ) {
			return	false;
		}
		return !_pendingEvents.isEmpty();
	}

	private void dispatchPendingEvent(final Closure dispatchEvent) {
		final Object nextEvent = popPendingEvent();
		
		if ( null != nextEvent ) {
			dispatchEvent.execute(nextEvent);
		}
		else {
			setUnactive();
		}
	}
	
	private void schedulePendingEvent(final Closure dispatchEvent) {
		getService().submit(new Runnable(){
			
			public void run() {
				dispatchPendingEvent(dispatchEvent);
			}});
	}
	
	private void checkIfSchedulePendingEvent(final Closure dispatchEvent) {
		if ( hasPendingEvent() ) {
			if ( setActived() ) {
				schedulePendingEvent(dispatchEvent);
			}
		}
	}
	
	public boolean setActived() {
		if ( isDestroyed() ) {
			return	false;
		}
		final boolean ret = _isActive.compareAndSet(false, true);
		if ( ret ) {
			_lastActiveTime = System.currentTimeMillis();
			if ( null != this._statusListener ) {
				try {
					this._statusListener.onActive(this);
				}
				catch (Exception e) {
					LOG.warn("throw exception when invoke this.statusListener.onActive", e);
				}
			}
		}
		return	ret;
	}

    public void setStateLiveTime(final String stateName,final long ttl) {
        _stateLiveTime.put(stateName, ttl);
    }

	private void setUnactive() {
		if ( _isActive.compareAndSet(true, false) ) {
			_activeTime.addAndGet(System.currentTimeMillis() - _lastActiveTime);
			if ( null != this._statusListener ) {
				try {
					this._statusListener.onUnactive(this);
				}
				catch (Exception e) {
					LOG.warn("throw exception when invoke this.statusListener.onUnactive", e);
				}
			}
		}
	}

	public void endOfDispatchEvent(final Closure dispatchEvent) {
		setUnactive();
		checkIfSchedulePendingEvent(dispatchEvent);
	}
	
	public void processEvent(final Object event, final Closure dispatchEvent) {
		pushPendingEvent(event);
		checkIfSchedulePendingEvent(dispatchEvent);
	}

	public void setStatusListener(final StatusListener listener) {
		this._statusListener = listener;
	}

	public long getTimeToActive() {
		return _activeTime.get();
	}

    public Map<String, Long> getStateLiveTime() {
        return Collections.unmodifiableMap( _stateLiveTime );
    }
}
